﻿/* 2011/11/18 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace Cosmos.Shortcuts
{
    /// <summary>
    /// Parses the mouse gesture made on a form component and draw mouse track on the component.
    /// </summary>
    public class MouseGestureParser
    {
        private const double _EFFICIENT_DISTANCE = 30;
        private const double _RADIAN_30 = Math.PI / 6;
        private const double _RADIAN_60 = Math.PI / 3;
        
        private bool _isMakingMouseGesture;
        private Point _previousMouseLocation;
        private bool _available;
        private LinkedList<MoveDirection> _moveDirectionList;
        private Graphics _componentGraphics;
        private MouseButtons _mouseButton;
        private Color _trackColor;
        private float _trackWidth;

        //private MoveDirection[] _originalTrack;
        //private MoveDirection[] _clearedTrack;        

        /// <summary>
        /// Initializes a new instance of MouseGestureParser class.
        /// </summary>
        public MouseGestureParser()
        {
            _mouseButton = MouseButtons.Right;
            _moveDirectionList = new LinkedList<MoveDirection>();
        }

        /// <summary>
        /// Initializes a new instance of MouseGestureParser with specified track color and track width.
        /// </summary>
        /// <param name="trackColor">The color of mouse track.</param>
        /// <param name="trackWidth">The width of mouse track.</param>
        public MouseGestureParser(Color trackColor, float trackWidth)
            : this()
        {
            _trackColor = trackColor;
            _trackWidth = trackWidth;
        }

        public void Register(Control control)
        {
            control.MouseDown += Control_MouseDown;
            control.MouseMove += Control_MouseMove;
            control.MouseUp += Control_MouseUp;
        }

        public void Disregister(Control control)
        {
            control.MouseDown -= Control_MouseDown;
            control.MouseMove -= Control_MouseMove;
            control.MouseUp -= Control_MouseUp;
        }

        public void GestureMouseDown(object sender, MouseEventArgs e)
        {
            Control component = sender as Control;
            _previousMouseLocation = e.Location;
            _moveDirectionList.Clear();
            _componentGraphics = component.CreateGraphics();
        }

        public void GestureMouseMove(object sender, MouseEventArgs e)
        {
            double moveDistanceX = e.X - _previousMouseLocation.X;
            double moveDistanceY = e.Y - _previousMouseLocation.Y;
            double moveDistance = Math.Sqrt(moveDistanceX * moveDistanceX + moveDistanceY * moveDistanceY);

            if (moveDistance >= _EFFICIENT_DISTANCE)
            {
                if (!_isMakingMouseGesture)
                    _isMakingMouseGesture = true;

                _componentGraphics.DrawLine(new Pen(_trackColor, _trackWidth)
                                , _previousMouseLocation, e.Location);
                _previousMouseLocation = e.Location;

                Direction moveDirection = Direction.Center;
                if (moveDistanceX == 0)
                {
                    if (moveDistanceY > 0)
                        moveDirection = Direction.South;
                    else
                        moveDirection = Direction.North;
                }
                else if (moveDistanceY == 0)
                {
                    if (moveDistanceX > 0)
                        moveDirection = Direction.East;
                    else
                        moveDirection = Direction.West;
                }
                else if (moveDistanceX > 0 && moveDistanceY > 0)
                {
                    double radian = Math.Atan(moveDistanceY / moveDistanceX);
                    if (radian < _RADIAN_30)
                        moveDirection = Direction.East;
                    else if (radian > _RADIAN_60)
                        moveDirection = Direction.South;
                    else
                        moveDirection = Direction.SouthEast;
                }
                else if (moveDistanceX < 0 && moveDistanceY > 0)
                {
                    double radian = Math.Atan(moveDistanceY / moveDistanceX);
                    if (radian > -_RADIAN_30)
                        moveDirection = Direction.West;
                    else if (radian < -_RADIAN_60)
                        moveDirection = Direction.South;
                    else
                        moveDirection = Direction.SouthWest;
                }
                else if (moveDistanceX < 0 && moveDistanceY < 0)
                {
                    double radian = Math.Atan(moveDistanceY / moveDistanceX);
                    if (radian < _RADIAN_30)
                        moveDirection = Direction.West;
                    else if (radian > _RADIAN_60)
                        moveDirection = Direction.North;
                    else
                        moveDirection = Direction.NorthWest;
                }
                else if (moveDistanceX > 0 && moveDistanceY < 0)
                {
                    double radian = Math.Atan(moveDistanceY / moveDistanceX);
                    if (radian > -_RADIAN_30)
                        moveDirection = Direction.East;
                    else if (radian < -_RADIAN_60)
                        moveDirection = Direction.North;
                    else
                        moveDirection = Direction.NorthEast;
                }
                _moveDirectionList.AddLast(new MoveDirection(moveDirection));
            }
            
        }

        public void GestureMouseUp(object sender, MouseEventArgs e)
        {
            Control component = sender as Control;
            _isMakingMouseGesture = false;
            _componentGraphics.Dispose();
            component.Refresh();

            /*
            _originalTrack = new MoveDirection[_moveDirectionList.Count];
            _moveDirectionList.CopyTo(_originalTrack, 0);
            for (LinkedListNode<MoveDirection> theNode = _moveDirectionList.First; theNode != null; theNode = theNode.Next)
            {
                while (theNode.Next != null && theNode.Value.Equals(theNode.Next.Value))
                {
                    _moveDirectionList.Remove(theNode.Next);
                }
            }
            _clearedTrack = new MoveDirection[_moveDirectionList.Count];
            _moveDirectionList.CopyTo(_clearedTrack, 0);*/

            Analyse();
            OnGestureCompleted(EventArgs.Empty);
        }

        protected void OnGestureCompleted(EventArgs e)
        {
            if (GestureCompleted != null)
                GestureCompleted(this, e);
        }

        /// <summary>
        /// By Analyzing the mouse movement, excludes redundant and invalid movement.
        /// </summary>
        private void Analyse()
        {
            _available = true;
            MoveDirection previousEfficientDirection = new MoveDirection(Direction.Center);
            for (LinkedListNode<MoveDirection> theNode = _moveDirectionList.First; theNode != null; theNode = theNode.Next)
            {
                RemoveRepetitiveAfterNode(theNode);

                MoveDirection currentDirection = theNode.Value;
                if (currentDirection.IsPrincipal)
                {
                    previousEfficientDirection = currentDirection;
                }
                else
                {
                    if (previousEfficientDirection.IsCenter)
                    {
                        // Initialized status.
                        if (theNode.Next != null && theNode.Next.Value.IsPrincipal)
                        {
                            // The next direction is a principal direction.
                            MoveDirection nextDirection = theNode.Next.Value;
                            if (currentDirection.IsAdjacent(nextDirection))
                            {
                                theNode.Value = nextDirection;
                                previousEfficientDirection = nextDirection;
                                RemoveRepetitiveBeforeNode(theNode);
                                RemoveRepetitiveAfterNode(theNode);
                            }
                            else
                            {
                                MoveDirection nextReverseDirection = nextDirection.Reverse();
                                theNode.Value = nextReverseDirection;
                                previousEfficientDirection = nextReverseDirection;
                                RemoveRepetitiveBeforeNode(theNode);
                            }
                        }
                        else
                        {
                            _available = false;
                            break;
                        }
                    }
                    else if (previousEfficientDirection.IsAdjacent(currentDirection))
                    {
                        LinkedListNode<MoveDirection> unavailableNode = theNode;
                        theNode = theNode.Previous;
                        _moveDirectionList.Remove(unavailableNode);
                        RemoveRepetitiveAfterNode(theNode);
                    }
                    else
                    {
                        theNode.Value = previousEfficientDirection.Reverse();
                        previousEfficientDirection = theNode.Value;
                        RemoveRepetitiveAfterNode(theNode);
                        RemoveRepetitiveBeforeNode(theNode);
                    }
                }
            }
            if (!_available)
                _moveDirectionList.Clear();
        }

        /// <summary>
        /// Removes the repetitive nodes after a specified node.
        /// </summary>
        private void RemoveRepetitiveAfterNode(LinkedListNode<MoveDirection> node)
        {
            MoveDirection currentDirection = node.Value;
            while (node.Next != null && currentDirection.Equals(node.Next.Value))
            {
                _moveDirectionList.Remove(node.Next);
            }
        }

        /// <summary>
        /// Removes the repetitive nodes before a specified node.
        /// </summary>
        /// <param name="node"></param>
        private void RemoveRepetitiveBeforeNode(LinkedListNode<MoveDirection> node)
        {
            MoveDirection currentDirection = node.Value;
            while (node.Previous != null && currentDirection.Equals(node.Previous.Value))
            {
                _moveDirectionList.Remove(node.Previous);
            }
        }

        private void Control_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == _mouseButton)
                GestureMouseDown(sender, e);
        }

        private void Control_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == _mouseButton)
                GestureMouseMove(sender, e);
        }

        private void Control_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == _mouseButton)
                GestureMouseUp(sender, e);
        }

        /// <summary>
        /// Gets the the mouse gesture representing the mouse track.
        /// </summary>
        public MouseGesture Gesture
        {
            get { return new MouseGesture(_moveDirectionList); }
        }

        /// <summary>
        /// Gets a value indicating whether the mouse is making gesture.
        /// </summary>
        public bool IsMakingGesture
        {
            get { return _isMakingMouseGesture; }
        }

        /// <summary>
        /// Gets or sets the mouse button which will be clicked to invoke mouse gesture.
        /// </summary>
        public MouseButtons Button
        {
            get { return _mouseButton; }
            set { _mouseButton = value; }
        }

        /// <summary>
        /// Gets and set the color of mouse track.
        /// </summary>
        public Color TrackColor
        {
            get { return _trackColor; }
            set { _trackColor = value; }
        }

        /// <summary>
        /// Gets and set the width of mouse track.
        /// </summary>
        public float TrackWidth
        {
            get { return _trackWidth; }
            set { _trackWidth = value; }
        }

        /// <summary>
        /// Occurs when a new mouse gesture has completed.
        /// </summary>
        public event EventHandler GestureCompleted;
    }
}
